<?php

namespace Ktnw\CurdSupport\Services\Impl;

use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Ktnw\CurdSupport\Params\AbstractBaseParams;
use Ktnw\CurdSupport\Services\BaseService;
use Ktnw\CurdSupport\Utils\BeanUtils;
use Ktnw\CurdSupport\Wrappers\PageInfo;
use Ktnw\CurdSupport\Wrappers\QueryConstants;
use Ktnw\CurdSupport\Wrappers\QueryResultVo;
use Ktnw\CurdSupport\Wrappers\QueryWrapperParams;
use Ktnw\CurdSupport\Wrappers\SaveOrUpdateWrapper;
use Prettus\Repository\Presenter\FractalPresenter;

/**
 * 基础service实现--提供公用接口
 */
class BaseServiceImpl implements BaseService
{

    /**
     * 查询model或实例
     * @param $primary int 主键
     * @param $modelClass string model的class
     * @param bool $isInstance 是否返回实例 true-是；false-否; 为false时，返回array数组.
     * @return Model|array
     * @throws Exception
     */
    public function findOne(int $primary, string $modelClass, bool $isInstance = true)
    {
        // 通过model查询
        $model = $this->instanceModel($modelClass);
        if (!$model) {
            return $isInstance ? null : [];
        }
        $where   = [];
        $where[] = [$model->getPrimaryKey(), '=', $primary];
        return $this->findOneFromModel($model, $where, $isInstance);
    }

    /**
     * 查询model或实例
     * @param $where array 条件
     * @param $modelClass string model的class
     * @param bool $isInstance 是否返回实例 true-是；false-否; 为false时，返回array数组.
     * @return Model|array
     * @throws Exception
     */
    public function findOneByKeys(array $where, string $modelClass, bool $isInstance = true)
    {
        // 通过model查询
        $model = $this->instanceModel($modelClass);
        if (!$model) {
            return $isInstance ? null : [];
        }
        return $this->findOneFromModel($model, $where, $isInstance);
    }

    /**
     * 根据model示例查询
     *
     * @param Model $model
     * @param array $where
     * @param bool $isInstance
     * @return array|Model
     * @throws Exception
     */
    private function findOneFromModel(Model $model, array $where, bool $isInstance = true)
    {
        $instance = $model->newQuery()->where($where)->first();
        if (!$instance) {
            return $isInstance ? null : [];
        }

        // 判断数据是否被逻辑删除
        // 获取逻辑删除标志
        $status = $instance->pseudoDeletionStatus();
        if ($status) {
            // 获取逻辑删除标志的值
            $delStatus = $instance->getAttribute($status[0]);
            if (isset($delStatus) && $delStatus == $status[1]) {
                return $isInstance ? null : [];
            }
        }

        return $isInstance ? $instance : $this->toArrayProcess($instance);
    }

    /**
     * delete实例
     * @param $primary int 主键
     * @param $modelClass string model的class
     * @param bool $pseudoDeletion 是否伪删除 true-是；false-否; true时做逻辑删除;false时，做物理删除.
     * @return bool
     * @throws Exception
     */
    public function delete(int $primary, string $modelClass, bool $pseudoDeletion = false): bool
    {
        $instance = $this->findOne($primary, $modelClass);
        if (!$instance) {
            throw new Exception("操作失败，原因：要删除的记录不存在。");
        }

        if ($pseudoDeletion) {
            // 逻辑删除
            return $instance->pseudoDeletion();
        }

        // 物理删除
        return $instance->delete();
    }


    /**
     * 保存实例
     * @param AbstractBaseParams $params object|array 封装实例的参数
     * @param $modelClass string model的class
     * @param SaveOrUpdateWrapper|null $mappers SaveOrUpdateWrapper
     * @param $ignoreColumn array 要忽略的字段
     * @return bool
     * @throws Exception
     */
    public function saveOrUpdate(AbstractBaseParams $params, string $modelClass, SaveOrUpdateWrapper $mappers = null, array $ignoreColumn = []): bool
    {
        return $this->saveOrMerge($params, $modelClass, $mappers, $ignoreColumn) > 0;
    }

    /**
     * 保存实例
     * @param AbstractBaseParams $params object|array 封装实例的参数
     * @param $modelClass string model的class
     * @param SaveOrUpdateWrapper|null $mappers SaveOrUpdateWrapper
     * @param $ignoreColumn array 要忽略的字段
     * @return int 实例id
     * @throws Exception
     */
    public function saveOrMerge(AbstractBaseParams $params, string $modelClass, SaveOrUpdateWrapper $mappers = null, array $ignoreColumn = []): int
    {
        $id       = $params->getPrimaryValue();
        $instance = null;
        if (!empty($id)) {
            $instance = $this->findOne($id, $modelClass);
            if (!$instance) {
                throw new Exception("操作失败，原因：要修改记录不存在。");
            }
        }

        // 这里调用校验类 校验通过后，往下执行
        if ($mappers) {
            $mappers->verify($params);
        }

        if (!$instance) {
            // 新增
            $instance       = $this->instanceModel($modelClass);
            $ignoreColumn[] = $instance->getPrimaryKey();
            BeanUtils::copyProps($params, $instance, $ignoreColumn, true);
            return $instance->save() ? $instance->getKey() : 0;
        } else {
            // 更新
            $ignoreColumn[] = $instance->getPrimaryKey();
            BeanUtils::copyProps($params, $instance, $ignoreColumn, true);
            return $instance->update() ? $instance->getKey() : 0;
        }

    }


    /**
     * 获取列表
     * @param $wrapperParams QueryWrapperParams
     * @return QueryResultVo
     * @throws Exception
     */
    public function findList(QueryWrapperParams $wrapperParams): QueryResultVo
    {
        // 根据queryType判断 是通过 model查询,还是通过DB::select()查询;
        if (!isset($wrapperParams) || !in_array($wrapperParams->getQueryType(), [QueryConstants::DB_MODEL, QueryConstants::DB_QUERY])) {
            $pageInfo = new PageInfo(0, $wrapperParams->getPage(), $wrapperParams->getSize());
            return new QueryResultVo($pageInfo, []);
        }

        if ($wrapperParams->getQueryType() == QueryConstants::DB_QUERY) {
            return $this->dbSelectByQueryWrapper($wrapperParams);
        }

        return $this->modelSelectByQueryWrapper($wrapperParams);
    }

    /**
     * DB::select查询
     * @param QueryWrapperParams $wrapperParams
     * @return QueryResultVo
     * @throws Exception
     */
    private function dbSelectByQueryWrapper(QueryWrapperParams $wrapperParams): QueryResultVo
    {
        $dataList = [];
        if (empty($wrapperParams->getSql())) {
            throw new Exception("query sql is null");
        }

        $totalSql = $wrapperParams->getTotalSql();
        $sql      = $wrapperParams->getSql();
        $params   = $wrapperParams->getParams();
        $page     = $wrapperParams->getPage();
        $size     = $wrapperParams->getSize();

        if (empty($totalSql)) {
            $totalSql = "SELECT count(*) from ({$sql}) sqlCount ";
        }

        // 查询总数
        $total = 0;
        $r     = $this->dbSelect($totalSql, $params, true);
        if ($r) {
            $isSet = false;
            foreach ($r as $v) {
                if ($isSet) {
                    break;
                }
                $total = $v;
                $isSet = true;
            }
        }

        // 查询明细
        if ($total > 0) {
            //设置分页
            if (strpos(strtolower($sql), "limit") === false && $page && $size) {
                $sql      .= " limit ? offset ? ";
                $params[] = $size;
                $params[] = ($page - 1) * $size;
            }
            $dataList = $this->dbSelect($sql, $params);
        }
        $pageInfo = new PageInfo($total, $page, $size);
        return new QueryResultVo($pageInfo, $dataList);
    }

    /**
     * 使用DB::select查询
     * @param $sql string 查询sql
     * @param $params array sql参数
     * @param $isSingle boolean 是否返回一条记录 true-是; false-否;
     * @return array
     */
    public function dbSelect(string $sql, array $params, bool $isSingle = false): array
    {
        if ($isSingle) {
            $r = DB::selectOne($sql, $params);
        } else {
            $r = DB::select($sql, $params);
        }
        if (empty($r)) {
            return [];
        }
        return $this->object_to_array($r);
    }


    /**
     * 使用model查询
     * @param QueryWrapperParams $wrapperParams
     * @return QueryResultVo
     * @throws Exception
     * @throws Exception
     */
    private function modelSelectByQueryWrapper(QueryWrapperParams $wrapperParams): QueryResultVo
    {
        $modelClass = $wrapperParams->getModelClass();
        if (empty($modelClass)) {
            throw new Exception("model class is null, please check it.");
        }

        // 查询参数
        $page = $wrapperParams->getPage();
        $size = $wrapperParams->getSize();

        // 通过model查询
        $model = $this->instanceModel($modelClass);
        if (!$model) {
            throw new Exception("model class is null, please check it.");
        }

        // 设置查询条件
        $builder = $model->newQuery()->where($wrapperParams->getWhere());
        $this->setWhereParams($wrapperParams, $builder);

        // 查询总数
        $total    = $builder->count();
        $dataList = [];
        if ($total > 0) {
            $dataList = $this->modelQueryList($model, $builder, $wrapperParams);
        }
        $pageInfo = new PageInfo($total, $page, $size);
        return new QueryResultVo($pageInfo, $dataList);
    }

    /**
     * 根据条件查询列表数据
     * @param Model $model
     * @param Builder $builder
     * @param QueryWrapperParams $wrapperParams
     * @return array
     * @throws Exception
     */
    private function modelQueryList(Model $model, Builder $builder, QueryWrapperParams $wrapperParams): array
    {
        // 获取主键
        $primaryKey = $model->getPrimaryKey();

        // 设置排序字段
        $orderBy = $wrapperParams->getOrderBy();
        if (empty($orderBy)) {
            // 默认根据主键降序排列
            $builder->orderBy($primaryKey, "desc");
        } else {
            foreach ($orderBy as $key => $val) {
                $builder->orderBy($key, $val);
            }
        }

        // 设置分页
        $page = $wrapperParams->getPage();
        $size = $wrapperParams->getSize();
        if (!empty($page) && !empty($size)) {
            $builder->offset(($page - 1) * $size)
                ->limit($size);
        }

        // 第一次查询只查询出主键
        $builder->select($primaryKey);
        $ids = $builder->get();
        if (!$ids) {
            return [];
        }

        // 第二次查询，查询出需要的字段
        $idList = [];
        foreach ($ids as $item) {
            $idList[] = $item->$primaryKey;
        }
        $builder    = $model->newQuery()->whereIn($primaryKey, $idList);
        $findColumn = $wrapperParams->getFindColumn();
        if (!empty($findColumn)) {
            $builder->select($findColumn);
        }
        if (empty($orderBy)) {
            // 默认根据主键降序排列
            $builder->orderBy($primaryKey, "desc");
        } else {
            foreach ($orderBy as $key => $val) {
                $builder->orderBy($key, $val);
            }
        }

        // 获取查询结果
        $details = $builder->get();
        if (!$details) {
            return [];
        }

        $presenter = $wrapperParams->getUsePresenter() ? $this->resolvePresenter($model) : null;
        if ($presenter) {
            $details = $presenter->present($details);
            $details = empty($details["data"]) ? [] : $details["data"];
        } else {
            $details = $details->toArray();
        }

        return $details;
    }


    /**
     * 使用ORM model 查询
     * @param string $modelClass
     * @param array $where 查询条件
     * @param array $orWhere
     * @param array $findColumn
     * @param array $inWhere
     * @param array $orderBy 排序
     * @param bool $singleton 是否查询单条数据
     * @return array
     * @throws Exception
     */
    public function modelSelect(string $modelClass, array $where, array $orWhere = [], array $findColumn = [], array $inWhere = [], array $orderBy = [], bool $singleton = false): array
    {
        $instance = $this->instanceModel($modelClass);
        $builder  = $instance->where($where);
        if (!empty($findColumn)) {
            $builder->select($findColumn);
        }
        if (!empty($orWhere)) {
            $builder->where(function ($query) use ($orWhere) {
                foreach ($orWhere as $value) {
                    $query->orWhere($value[0], $value[1], $value[2]);
                }
            });
        }
        if (!empty($inWhere)) {
            foreach ($inWhere as $filed => $list) {
                $builder->whereIn($filed, $list);
            }
        }

        if (!empty($orderBy)) {
            foreach ($orderBy as $key => $val) {
                $builder->orderBy($key, $val);
            }
        }
        if ($singleton) {
            $details = $builder->first();
        } else {
            $details = $builder->get();
        }

        if (!$details) {
            return [];
        }
        $presenter = $this->resolvePresenter($instance);
        if ($presenter) {
            $details = $presenter->present($details);
            $details = empty($details["data"]) ? [] : $details["data"];
        } else {
            $details = $details->toArray();
        }
        return $details;
    }


    /**
     * 通用的修改数据库方法
     * @param string $sql
     * @param array $params
     * @return bool
     */
    public function dbUpdate(string $sql, array $params): bool
    {
        if (empty($sql)) {
            return false;
        }
        return DB::update($sql, $params) > 0;
    }

    /**
     * 删除
     * @param $sql
     * @param $params
     * @return bool
     */
    public function dbDelete($sql, $params): bool
    {
        if (empty($sql)) {
            return false;
        }
        return DB::delete($sql, $params) > 0;
    }


    /**
     * 设置查询条件
     * @param QueryWrapperParams $wrapperParams
     * @param $builder
     */
    private function setWhereParams(QueryWrapperParams $wrapperParams, &$builder): void
    {
        $orWhere = $wrapperParams->getOrWhere();
        if (!empty($orWhere)) {
            $builder->where(function ($query) use ($orWhere) {
                foreach ($orWhere as $value) {
                    $query->orWhere($value[0], $value[1], $value[2]);
                }
            });
        }
        $inWhere = $wrapperParams->getInWhere();
        if (!empty($inWhere)) {
            foreach ($inWhere as $filed => $list) {
                $builder->whereIn($filed, $list);
            }
        }

        $notInWhere = $wrapperParams->getNotInWhere();
        if (!empty($notInWhere)) {
            foreach ($notInWhere as $filed => $list) {
                $builder->whereNotIn($filed, $list);
            }
        }
    }


    /**
     * new model实例
     * @param $modelClass
     * @return Model
     */
    private function instanceModel($modelClass): Model
    {
        return new $modelClass();
    }

    /**
     * 获取presenter
     * @param $model Model 实例
     * @return FractalPresenter
     */
    private function resolvePresenter(Model $model): ?FractalPresenter
    {
        $class = explode("\\", get_class($model));
        $class = "App\Presenters\\" . end($class) . "Presenter";
        try {
            return resolve($class);
        } catch (Exception $e) {
            // Log::info($e->getMessage().", not use Presenter.")
        }
        return null;
    }

    /**
     * 判断是否存在presenter
     * 若存在使用presenter处理
     *
     * @param $model Model 实例
     * @return array
     * @throws Exception
     */
    private function toArrayProcess(Model $model): array
    {
        $presenter = $this->resolvePresenter($model);
        if ($presenter) {
            $r = $presenter->present($model);
            return empty($r["data"]) ? [] : $r["data"];
        }
        return $model->toArray();
    }

    /**
     * 对象转换数组
     * @param $obj
     * @return array
     */
    private function object_to_array($obj): array
    {
        if (!is_array($obj) && !is_object($obj)) {
            return $obj;
        }
        $_arr = is_object($obj) ? get_object_vars($obj) : $obj;
        $arr  = [];
        foreach ($_arr as $key => $val) {
            $val       = (is_array($val)) || is_object($val) ? $this->object_to_array($val) : $val;
            $arr[$key] = $val;
        }
        return $arr;
    }


}